package modele;

import java.util.ArrayList;
import java.util.Iterator;

public class SolveurSimplexe {
	//Variables de classes statiques
	
	//Variables d'instance
	private String nomProb;
	private int dirFctObjectif;		//MIN ou MAX
	private FonctionAOptimiser fonctionAOptimiser = null;
	private ArrayList<Equation> vectEquation = new ArrayList<Equation> ();
	private ArrayList<PointIntersection> listeDesPointsDeLespaceConvexe = new ArrayList<PointIntersection> ();
	private boolean aFaisLeCalcul = false ;
	
	public SolveurSimplexe() {
	}
	
	/**
	 * Regle le nom du probleme courant
	 * @param nomProb Le nom du probleme courant
	 */
	public void setNomProb(String nomProb) {
		this.nomProb = nomProb;
	}
	
	
	
	
	public void ajouterEquation(Equation e) {
		aFaisLeCalcul = false ;
		this.vectEquation.add(e) ;
	}
	
	
	
	
	
	
	/**
	 * Regle la direction de l'optimisation (minimiser ou maximiser)
	 * @param dir Un code de direction entre MIN ou MAX
	 */
	public void setFonctionAOptimmiser(double A,double B,int dirFctObjectif) {
		this.fonctionAOptimiser = new FonctionAOptimiser(A,B,dirFctObjectif) ;
	}
	
	/**
	 * Calcule et retourne l'ensemble des solutions realisables
	 * a la fin,
	 * on a fin on a la liste des points realisable dans la variable
	 * listeDesPointsDeLespaceConvexe.
	 * @throws Exception
	 */
	public void calculPointsRealisables() throws Exception {
		
		Iterator<Equation> it1 = vectEquation.iterator();
		
		int indiceBoucle1 = 0 ;
		Equation eq1 = null;
		Equation eq2 = null;
		while (it1.hasNext()) {
			indiceBoucle1++ ;
			
			eq1 = it1.next();
			Iterator<Equation> it2 = vectEquation.iterator();
			for (int i = 0; i < indiceBoucle1; i++) {
				it2.next() ;
			}
			while (it2.hasNext()) {
				eq2 = it2.next();
				try {
					PointIntersection pi = calculerIntersection(eq1, eq2);
					Iterator<Equation> it3 = vectEquation.iterator();
					Equation e = it3.next();
					while (it3.hasNext() && e.verifierSiValide(pi)) {
						e = it3.next();
					}
					// si on est au dernier element
					if (!it3.hasNext()) {
						// le dernier element est valide
						if (e.verifierSiValide(pi)) {
							// le point est valide pour
							// toutes les equations
							// on l'ajoute a la liste du contour
							pi.setDroites(eq1.getNumEq(),eq2.getNumEq()) ;
							this.listeDesPointsDeLespaceConvexe.add(pi) ;
						}
					}
				}
				catch (DroitesParallelesException e) {
					System.err.println("droites paralleles :"+eq1+","+eq2);
				}
				catch (DroitesCoincidentesException e) {
					System.err.println("droites coincidentes :"+eq1+","+eq2);
				}
			}
		}
		aFaisLeCalcul = true ;
	}
	
	/**
	 * Calcule les intersections entre deux droites d'equation eq1 et eq2
	 * @param eq1-->point (4.0,0.0)
--->point (4.0,1.0)
--->point (0.0,3.0)
--->point (0.0,0.0)
--->point (3.0,3.0)
fin
	 * @param eq2
	 * @return 
	 */
	private PointIntersection calculerIntersection(Equation eq1, Equation eq2) throws DroitesParallelesException,DroitesCoincidentesException{
		if (eq1.getA()/eq2.getA()==eq1.getB()/eq2.getB())
		{
			if(eq1.getA()/eq2.getA()==eq1.getC()/eq2.getC()){
				throw new DroitesCoincidentesException() ;
			}
			throw new DroitesParallelesException() ;
		}
		
		
		double x = (eq2.getB()*eq1.getC() - eq1.getB()*eq2.getC())/(eq1.getA()*eq2.getB()-eq2.getA()*eq1.getB());
		double y = (eq1.getA()*eq2.getC() - eq2.getA()*eq1.getC())/(eq1.getA()*eq2.getB()-eq2.getA()*eq1.getB());
		return new PointIntersection(x,y) ;
	}

	public ArrayList<PointIntersection> getPointsDeLespaceConvexe() throws Exception {
		if (aFaisLeCalcul)
			return listeDesPointsDeLespaceConvexe;
		throw new Exception("le solveur ne peut rendre de resultat, le calcul n'est pas encore fait") ;
	}
	
	
	
	public ArrayList<PointIntersection> getCheminVersResultat() throws Exception {
		return getCheminVersResultat(this.listeDesPointsDeLespaceConvexe.get(0)) ;
	}
	
	public ArrayList<PointIntersection> getCheminVersResultat(PointIntersection depart) throws Exception {
		if (!aFaisLeCalcul) {
			throw new Exception("le solveur ne peut rendre de resultat, le calcul n'est pas encore fait") ;
		}
		if (fonctionAOptimiser==null) {
			throw new Exception("le solveur ne peut rendre de resultat, pas de fonction a optimiser") ;
		}
		ArrayList<PointIntersection> voisins = this.getVoisins(depart) ;
		
		
		ArrayList<PointIntersection> res = new ArrayList<PointIntersection> () ;
		res.add(depart) ;
		
//		System.out.println("point "+depart+" : nb de voisins="+voisins.size());
		
		if (voisins.size()==0) {
			// ca veut dire qu'il n'y a que 2 droites,
			// et 1 seul point resultat.
			// le résultat est forcément le point.
			return res ;
		}
		
		if (voisins.size()==1) {
			// si on a qu un seul voisin,
			// ca veut dire que si il fais mieu pour la 
			// fonction a optimiser, 
			// on doit sauter a ce point,
			// sinon on est dejas au point optimal
			if (deuxiemePointEstIlPlusOptimal(depart,voisins.get(0)))
			{
				res.add(voisins.get(0)) ;
				ArrayList<PointIntersection> suivants = this.getCheminVersResultat(voisins.get(0)) ;
				for (PointIntersection intersection : suivants) {
					res.add(intersection) ;
				}
			}else {
				return res ;
			}
			return res ;
		}
		
		if (voisins.size()==2) {
			// si on a qu un seul voisin,
			// ca veut dire que si il fais mieu pour la 
			// fonction a optimiser, 
			// on doit sauter a ce point,
			// sinon on est dejas au point optimal
			if (deuxiemePointEstIlPlusOptimal(depart,voisins.get(0)))
			{
				res.add(voisins.get(0)) ;
				ArrayList<PointIntersection> suivants = this.getCheminVersResultat(voisins.get(0)) ;
				for (PointIntersection intersection : suivants) {
					res.add(intersection) ;
				}
			}else{
			if (deuxiemePointEstIlPlusOptimal(depart,voisins.get(1)))
			{
				res.add(voisins.get(1)) ;
				ArrayList<PointIntersection> suivants = this.getCheminVersResultat(voisins.get(1)) ;
				for (PointIntersection intersection : suivants) {
					res.add(intersection) ;
				}
			}
			}
			return res ;
		}
		if (voisins.size()>2) {
			// on a plus de 2 voisins
			throw new Exception("Nombre de voisins du point "+depart+" trop eleve:"+voisins.size()) ;
		}
		throw new Exception("Nombre de voisins du point "+depart+" impossible:"+voisins.size()) ;
	}

	
	
	
	
	
	private boolean deuxiemePointEstIlPlusOptimal(PointIntersection point1, PointIntersection point2) throws Exception {
		if (this.fonctionAOptimiser.getDirection()==FonctionAOptimiser.MAXIMISER) {
			// on cherche a maximiser
			return ((this.fonctionAOptimiser.getA()*point1.getX() + this.fonctionAOptimiser.getB()*point1.getY()) < (this.fonctionAOptimiser.getA()*point2.getX() + this.fonctionAOptimiser.getB()*point2.getY())) ;
		}
		if (this.fonctionAOptimiser.getDirection()==FonctionAOptimiser.MINIMISER) {
			// on cherche a maximiser
			return ((this.fonctionAOptimiser.getA()*point1.getX() + this.fonctionAOptimiser.getB()*point1.getY()) > (this.fonctionAOptimiser.getA()*point2.getX() + this.fonctionAOptimiser.getB()*point2.getY())) ;
		}
		throw new Exception("la fonction a maximiser n est pas correcte "+this.fonctionAOptimiser) ;
	}
	
	
	
	

	private ArrayList<PointIntersection> getVoisins(PointIntersection depart) {
		ArrayList<PointIntersection> res = new ArrayList<PointIntersection> () ;

		for (PointIntersection pi : listeDesPointsDeLespaceConvexe) {
			if (pi!=depart)
			{
//				System.out.println("points differents ="+pi+","+depart);
				if (    pi.getDroite1()==depart.getDroite1() ||
						pi.getDroite1()==depart.getDroite2() ||
						pi.getDroite2()==depart.getDroite1() ||
						pi.getDroite2()==depart.getDroite2() 
				) {
//					System.out.println(">"+pi.toString());
					res.add(pi) ;
				}
			}
		}
			return res ;
	}
	
	
	
	
	
	
}
